package paim.wingchun.app;


import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Frame;
import java.awt.Graphics2D;
import java.awt.SplashScreen;
import java.awt.Window;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.net.BindException;
import java.nio.channels.FileLock;
import java.util.HashSet;
import java.util.Set;
import java.util.logging.Level;

import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
import javax.swing.ToolTipManager;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.WindowConstants;

import org.hibernate.Session;

import com.jgoodies.looks.Options;
import com.jgoodies.looks.plastic.PlasticLookAndFeel;
import com.jgoodies.looks.plastic.PlasticXPLookAndFeel;
import com.jgoodies.looks.plastic.theme.Silver;

import paim.wingchun.controller.AbstractController;
import paim.wingchun.view.AbstractView;
import paim.wingchun.view.Dialogs;
import paim.wingchun.view.GUIUtils;
import paim.wingchun.view.IVisaoApp;

public abstract class WC_App<VisaoApp extends IVisaoApp> extends WindowAdapter {

    /** reconhece informacoes sobre aplicacao, ininicaliza sistema de LOG ou cai fora */
    static {
        try {
            WC_Properties.verificaPropertiesApp();

            /* simplismente inicializa constantes dependentes das propriedades da aplicacao */
            new Constantes();

            /* verificar dir home da aplicacao, */
            System.out.println("OK. app Home Path: [" + WC_File.getAppHomePath() + "]");

            /* inicia sistema de log */
            new WC_Log();
        }
        catch ( Throwable e ) {
            e.printStackTrace();
            throw new Error("Aplicacao nao pode ser inicializada");
        }
        if ( WC_Log.getInstancia() == null )
            throw new Error("Log não inicializou");
        WC_Log.getInstancia().getlogger().log(Level.INFO, "iniciando aplicação");
    }

    protected static WC_App<?> instancia;

    protected static FileLock fileLock;

    protected VisaoApp visaoApp;

    private static Session sessao;

    /* lista de controladores ativos */
    protected static Set<AbstractController<?, ?>> abstractControllers = new HashSet<>();

    protected AbstractView<?> hasFocus;

    private volatile boolean saindo = false;

    private transient SplashScreen splash;

    private transient Graphics2D splashGraphics2D;

    /** versao */
    public static final String App$Versao = Constantes._versao + "." + Constantes._subversao + "."
                    + Constantes._release;

    // private static HelpCamel help;

    protected void renderSplashFrame(String msg) {
        if ( splashGraphics2D != null ) {
            splashGraphics2D.setComposite(AlphaComposite.Clear);
            splashGraphics2D.fillRect(120, 140, 200, 40);
            splashGraphics2D.setPaintMode();
            splashGraphics2D.setColor(Color.BLACK);
            splashGraphics2D.drawString("Loading " + msg + "...", 120, 150);
            splash.update();
        }
    }

    protected void initializeSplash() {
        splash = SplashScreen.getSplashScreen();
        if ( splash == null )
            WC_Log.getInstancia().getlogger().log(Level.WARNING, "SplashScreen.getSplashScreen() returned null");
        else
            splashGraphics2D = splash.createGraphics();
    }

    /** @author paim 02/03/2012 void */
    protected void splashStop() {
        if ( splash != null )
            splash.close();
    }

    protected WC_App() {
        instancia = this;

        initializeSplash();

        renderSplashFrame("HSQLDB");
        if ( !initializeHSQLDB() ) {
            /* tonto cocoduche! */
            splashStop();
            System.exit(1);
        }
        renderSplashFrame("Hibernate");
        if ( !initializeHibernate() ) {
            /* tonto cocoduche! */
            splashStop();
            System.exit(1);
        }

        antesInstanciaVisao();
        renderSplashFrame("Visão");
        instanciaVisao();
        aposInstanciaVisao();

        if ( !initializeUIManager() )
            /* tonto cocoduche! */
            System.exit(1);
    }

    public static WC_App<?> getInstancia() {
        return instancia;
    }

    public static synchronized void setSessao(Session sessao) {
        WC_App.sessao = sessao;
    }

    public static synchronized Session getSessao() {
        return sessao;
    }

    protected boolean initializeUIManager() {
        boolean lafOK = false;

        WC_Log.getInstancia().getlogger().log(Level.INFO, "sistema   ----" + System.getProperty("os.name") + "----");
        /* http://mindprod.com/jgloss/properties.html#OSNAME */
        /* http://bugs.sun.com/view_bug.do?bug_id=6819886 */
        try {

            PlasticXPLookAndFeel laf = new PlasticXPLookAndFeel();
            PlasticLookAndFeel.setCurrentTheme(new Silver());
            PlasticLookAndFeel.setTabStyle(PlasticLookAndFeel.TAB_STYLE_METAL_VALUE);
            PlasticLookAndFeel.setHighContrastFocusColorsEnabled(true);
            PlasticLookAndFeel.set3DEnabled(true);

            // Plastic3DLookAndFeel laf = new Plastic3DLookAndFeel();
            // PlasticLookAndFeel.setCurrentTheme(new BrownSugar());
            // PlasticLookAndFeel.setTabStyle(PlasticLookAndFeel.TAB_STYLE_DEFAULT_VALUE);
            // PlasticLookAndFeel.setHighContrastFocusColorsEnabled(false);
            // PlasticLookAndFeel.set3DEnabled(false);

            // // NimbusLookAndFeel nimbus = new NimbusLookAndFeel();
            // try {
            // for ( LookAndFeelInfo info : UIManager.getInstalledLookAndFeels() ) {
            // if ( "Nimbus".equals(info.getName()) ) {
            // UIManager.setLookAndFeel(info.getClassName());
            // break;
            // }
            // }
            // }
            // catch ( Exception e ) {
            // // If Nimbus is not available, you can set the GUI to another look and feel.
            //
            // /*Do not set the Nimbus look and feel explicitly by invoking the UIManager.setLookAndFeel
            // method because not all versions or implementations of Java SE 6 support Nimbus.
            // Additionally, the location of the Nimbus package changed between the JDK 6 Update 10
            // and JDK 7 releases. Iterating through all installed look and feel implementations is a more
            // robust approach because if Nimbus is not available, the default look and feel is used.
            // For the JDK 6 Update 10 release, the Nimbus package is located at
            // com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel.*/
            // }
            /* tenta colocar esse LookAndFeel */
            UIManager.setLookAndFeel(laf);
            lafOK = true;
        }
        catch ( UnsupportedLookAndFeelException e ) {
            WC_Log.getInstancia().getlogger().log(Level.WARNING, e.getMessage(), e);

            try {
                /* tenta colocar o padrao para esse sistema */
                UIManager.setLookAndFeel(Options.getSystemLookAndFeelClassName());
                lafOK = true;
            }
            catch ( ClassNotFoundException | InstantiationException | IllegalAccessException
                            | UnsupportedLookAndFeelException e1 ) {
                WC_Log.getInstancia().getlogger().log(Level.SEVERE, e.getMessage(), e);
            }

        }
        return lafOK;
    }

    /** retorna true se pode instanciar aplicacao */
    protected static boolean antesInstanciarApp(String[] args) {
        if ( !initializeFileLock() )
            return false;
        return true;
    }

    /** procedimentos antes de sair da aplicacao */
    public void antesSairApp() {
        WC_App.getInstancia().finalizeFileLock();
        WC_App.getInstancia().finalizeHSQLDB();

    }

    public void sairApp() {
        setSaindo(true);

        WC_Log.getInstancia().getlogger().log(Level.INFO, "Shutting down...");

        if ( WC_App.getInstancia() == null )
            System.exit(1);

        // Controle.getInstancia().fecharDeclaracao(true);
        Thread t = new Thread("SairApp") {

            @Override
            public void run() {
                try {
                    WC_App.getInstancia().antesSairApp();

                    sleep(300);

                    if ( WC_App.getInstancia().getVisaoApp() != null )
                        ((JFrame) WC_App.getInstancia().getVisaoApp()).dispose();
                    // TODO colocar janelina progresso bla bla em thread aqui

                }
                catch ( InterruptedException e ) {
                    WC_Log.getInstancia().getlogger().log(Level.SEVERE, e.getMessage(), e);
                }
                finally {
                    /* finaliza LOG */
                    WC_Log.getInstancia().done();
                    /* sai mesmo */
                    System.exit(0);
                }
            }
        };
        t.start();

        // while ( t.isAlive() ) {
        // System.out.println("ta");
        // }

    }

    public synchronized void setSaindo(boolean saindo) {
        this.saindo = saindo;
    }

    public synchronized boolean isSaindo() {
        return this.saindo;
    }

    public static Set<AbstractController<?, ?>> getControladors() {
        return abstractControllers;
    }

    protected abstract Class<VisaoApp> getVClass();

    public void instanciaVisao() {
        try {
            visaoApp = getVClass().newInstance();
        }
        catch ( InstantiationException | IllegalAccessException e ) {
            WC_Log.getInstancia().getlogger().log(Level.SEVERE, e.getMessage(), e);
        }

        ((JFrame) visaoApp).setExtendedState(Frame.MAXIMIZED_BOTH);
        ((JFrame) visaoApp).addWindowListener(this);

        // TODO inicializar tooltip
        ToolTipManager.sharedInstance().setInitialDelay(500);
        ToolTipManager.sharedInstance().setReshowDelay(5000);
        ToolTipManager.sharedInstance().setDismissDelay(20000);
        ToolTipManager.sharedInstance().setLightWeightPopupEnabled(false);

        // FIXME colocar fim de splash aqui
        // if(splashScreen != null) {
        // splashScreen.stop();
        // splashScreen.dispose();
        // splashScreen = null;
        // }

        ((Window) visaoApp).setVisible(true);

        /* o listener vai resolver */
        ((JFrame) visaoApp).setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
        // ((JFrame) visaoApp).setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        /** Encerra a aplicacao ao fechar a janela */
        // this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

    }

    protected void aposInstanciaVisao() {
        SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                StringBuilder titulo = new StringBuilder();
                titulo.append(Constantes.App$Sigla);
                titulo.append("  ");
                titulo.append(Constantes.App$Nome);
                titulo.append("  ");
                titulo.append(App$Versao);
                if ( !Constantes.ESTAGIO.equals(Estagio.OMEGA) ) {
                    titulo.append(" - ");
                    titulo.append(Constantes.ESTAGIO.toString());
                }

                ((Frame) WC_App.this.getVisaoApp()).setTitle(titulo.toString());

                ImageIcon imageIcon = GUIUtils.getImageIcon("app.icon");
                ((Frame) getVisaoApp()).setIconImage(imageIcon.getImage());
            }
        });

    }

    protected void antesInstanciaVisao() {}

    public AbstractView<?> getHasFocus() {
        return this.hasFocus;
    }

    public void setHasFocus(AbstractView<?> hasFocus) {
        this.hasFocus = hasFocus;
    }

    public VisaoApp getVisaoApp() {
        return this.visaoApp;
    }

    @Override
    public void windowClosed(WindowEvent e) {
        System.out.println("Janela aplicação " + Constantes.App$Sigla + " Fechada");
    }

    @Override
    public void windowClosing(WindowEvent e) {
        WC_Log.getInstancia().getlogger().log(Level.INFO, "Fechando janela Aplicação " + Constantes.App$Sigla);
        sairApp();
    }

    private static boolean initializeFileLock() {
        try {
            RandomAccessFile raf = new RandomAccessFile(Constantes.App$LockFile, "rw");
            fileLock = raf.getChannel().tryLock();
            if ( fileLock == null ) {
                Dialogs.erro(null, "Aplicacao já está em execução", Constantes.App$Sigla + " " + App$Versao);
                System.exit(1);
                return false;
            }
            return true;
        }
        catch ( Throwable e ) {
            WC_Log.getInstancia().getlogger().log(Level.SEVERE, e.getMessage(), e);
            Dialogs.erro(null, Constantes.App$Nome, "arquivo TryLock.txt ");
            System.exit(1);
            return false;
        }
    }

    private void finalizeFileLock() {
        if ( fileLock == null )
            return;

        try {
            fileLock.release();
            fileLock.channel().close();
            new File(Constantes.App$LockFile).delete();
        }
        catch ( IOException e ) {
            WC_Log.getInstancia().getlogger().log(Level.SEVERE, e.getMessage(), e);
        }
    }

    private void finalizeHSQLDB() {
        try {
            /* Se o banco ainda nao fechou, entao tenta fechar! */
            if ( HyperSQLs.isOnline() ) {
                WC_Log.getInstancia().getlogger().log(Level.INFO, "Shutdown HSQLDB");
                HyperSQLs.shutdown();
            }
        }
        catch ( Exception e ) {
            WC_Log.getInstancia().getlogger().log(Level.SEVERE, e.getMessage(), e);
        }
    }

    private boolean initializeHSQLDB() { // TODO log
        try {
            HyperSQLs.start();
            /* se nao esta executando... */
            if ( !HyperSQLs.isOnline() ) {
                /* FIXME vai chegar aqui se tiver OUTRA aplicacao com hsqldb rodando */
                HyperSQLs.getServidor().getServerError();
                throw new Exception(HyperSQLs.getServidor().getServerError());
            }
        }
        catch ( BindException e ) {
            Dialogs.menssagem("O sistema já está em execução neste computador."
                            + "\nFeche a instância atual antes de iniciar outra execuïção.", "Erro na inicialização");
            return false;
        }
        catch ( Throwable e ) {
            // FIXME splashcreen
            // if(splashScreen != null) {
            // splashScreen.stop();
            // splashScreen.dispose();
            // }
            // splashScreen = null;
            Dialogs.erro("Erro ao iniciar banco de dados local." + "\nDescrição do erro: " + e.getMessage(),
                            "Erro na inicialização");
            WC_Log.getInstancia().getlogger().log(Level.SEVERE, e.getMessage(), e);
            return false;
        }
        return true;
    }

    private boolean initializeHibernate() {
        try {
            new Hibernates();

            /* Cria sessao hibernate. */
            setSessao(Hibernates.openSession());
            /* Testa se retorna conexao com banco */
            if ( getSessao().connection() == null )
                return false;
        }
        catch ( Exception e ) {
            Dialogs.menssagem("Conexï¿½o com o Banco de Dados falhou!\n" + e.getMessage(), "Informaï¿½ï¿½es do HSQLDB");
            WC_Log.getInstancia().getlogger().log(Level.SEVERE, e.getMessage(), e);
            return false;
        }
        return true;
    }

}
